package com.dbms;

import com.dbms.catalog.Catalog;
import com.dbms.common.Column;
import com.dbms.common.Schema;
import com.dbms.common.Tuple;
import com.dbms.common.Type;
import com.dbms.execution.execution.*;
import com.dbms.storage.BufferPoolManager;
import com.dbms.storage.DiskManager;
import com.dbms.transaction.LogManager;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 * 连接算法性能对比测试程序
 * 功能：独立测试并对比三种主流数据库连接算法的性能差异，帮助理解不同场景下的最优选择
 * 测试算法包括：
 * 1. Nested Loop Join (嵌套循环连接)：简单直观，适合小表连接
 * 2. Hash Join (哈希连接)：通过哈希表加速匹配，适合大表连接
 * 3. Index Nested Loop Join (索引嵌套循环连接)：利用索引优化内表查询，适合内表有索引的场景
 *
 * 测试场景设计：
 * - 大表 "orders"（订单表）：存储大量订单记录
 * - 小表 "customers"（顾客表）：存储少量顾客信息
 * - 查询目标：关联订单与对应顾客信息（通过顾客ID匹配）
 */
public class JoinPerformanceTest {

    // --- 测试参数配置 ---
    private static final int CUSTOMER_COUNT = 500;    // 顾客表记录数（小表，作为连接的内表）
    private static final int ORDER_COUNT = 50000;     // 订单表记录数（大表，作为连接的外表）
    private static final String DB_FILE_NAME = "join_perf_test.db"; // 测试用数据库文件
    private static final String LOG_FILE_NAME = "join_perf_test.log"; // 测试用日志文件

    /**
     * 程序主入口：执行连接算法性能测试的完整流程
     * @param args 命令行参数（未使用）
     * @throws IOException 磁盘IO操作异常（如文件创建失败）
     */
    public static void main(String[] args) throws IOException {
        System.out.println("============== Join 算法性能对比演示开始 ==============");

        // --- 1. 数据准备阶段：初始化环境、创建表结构、插入测试数据 ---
        // 此阶段耗时不计入性能对比，仅为测试提供基础数据
        System.out.println("\n--- 1. 数据准备阶段 ---");
        long setupStartTime = System.nanoTime(); // 准备阶段开始时间（纳秒级，高精度计时）

        // 清理旧测试文件，确保环境干净
        new File(DB_FILE_NAME).delete();
        new File(LOG_FILE_NAME).delete();

        // 初始化数据库核心组件
        DiskManager diskManager = new DiskManager(DB_FILE_NAME); // 磁盘管理器：处理物理文件读写
        LogManager logManager = new LogManager(LOG_FILE_NAME);   // 日志管理器：记录事务日志
        BufferPoolManager bpm = new BufferPoolManager(200, diskManager, logManager); // 缓冲池：缓存数据页，减少磁盘IO
        Catalog catalog = new Catalog(bpm, true); // 元数据目录：管理表结构、索引等信息

        // 创建 customers 表（内表）：存储顾客信息，主键c_id自动创建B+树索引
        Schema customerSchema = new Schema(List.of(
                new Column("c_id", Type.INTEGER, true),   // 顾客ID（主键，唯一标识）
                new Column("c_name", Type.VARCHAR, 50)    // 顾客名称（字符串类型，长度50）
        ));
        // 执行建表操作
        new CreateTableExecutor(catalog, "customers", customerSchema).next();

        // 创建 orders 表（外表）：存储订单信息，o_customer_id关联顾客表
        Schema orderSchema = new Schema(List.of(
                new Column("o_id", Type.INTEGER, true),       // 订单ID（主键）
                new Column("o_customer_id", Type.INTEGER),    // 关联的顾客ID（外键）
                new Column("o_info", Type.VARCHAR, 100)       // 订单详情（字符串类型，长度100）
        ));
        // 执行建表操作
        new CreateTableExecutor(catalog, "orders", orderSchema).next();

        // 插入顾客数据：循环创建CUSTOMER_COUNT条顾客记录
        Catalog.TableMetadata customerTable = catalog.getTable("customers");
        for (int i = 1; i <= CUSTOMER_COUNT; i++) {
            // 创建顾客元组：c_id = i，c_name = "Customer_Name_" + i
            Tuple tuple = new Tuple(List.of(i, "Customer_Name_" + i), customerSchema);
            // 执行插入操作
            new InsertExecutor(bpm, customerTable, Collections.singletonList(tuple)).next();
        }

        // 插入订单数据：循环创建ORDER_COUNT条订单记录，随机关联顾客
        Catalog.TableMetadata orderTable = catalog.getTable("orders");
        Random random = new Random(); // 随机数生成器：用于随机关联顾客ID
        for (int i = 1; i <= ORDER_COUNT; i++) {
            // 随机生成1~CUSTOMER_COUNT之间的顾客ID，确保订单关联到已存在的顾客
            int customerId = random.nextInt(CUSTOMER_COUNT) + 1;
            // 创建订单元组：o_id = i，o_customer_id = 随机顾客ID，o_info = "Order_Info_" + i
            Tuple tuple = new Tuple(List.of(i, customerId, "Order_Info_" + i), orderSchema);
            // 执行插入操作
            new InsertExecutor(bpm, orderTable, Collections.singletonList(tuple)).next();
        }

        // 刷新缓冲池所有数据到磁盘，确保后续查询基于稳定的磁盘数据
        bpm.flushAllPages();

        // 计算并打印准备阶段耗时
        long setupEndTime = System.nanoTime();
        System.out.printf("数据准备完成！共插入 %d 顾客和 %d 订单。耗时: %.3f ms\n",
                CUSTOMER_COUNT, ORDER_COUNT, (setupEndTime - setupStartTime) / 1_000_000.0);
        System.out.println("--------------------------------------------------\n");


        // --- 2. 性能测试阶段：分别用三种连接算法执行查询并计时 ---
        System.out.println("--- 2. Join 查询性能测试阶段 ---");
        System.out.println("    查询: SELECT * FROM orders JOIN customers ON customers.c_id = orders.o_customer_id;");

        // 定义连接结果的最终 schema：合并订单表和顾客表的所有列
        List<Column> finalColumns = new ArrayList<>();
        finalColumns.addAll(orderSchema.getColumns());   // 先添加订单表的列（o_id, o_customer_id, o_info）
        finalColumns.addAll(customerSchema.getColumns()); // 再添加顾客表的列（c_id, c_name）
        Schema finalSchema = new Schema(finalColumns);

        // --- 测试1: Nested Loop Join（嵌套循环连接）---
        double nlJoinMs = testNestedLoopJoin(bpm, catalog, finalSchema);
        System.out.printf("\n--> 测试1: [Nested Loop Join]  耗时: %.3f 毫秒\n", nlJoinMs);

        // --- 测试2: Hash Join（哈希连接）---
        double hashJoinMs = testHashJoin(bpm, catalog, finalSchema);
        System.out.printf("--> 测试2: [Hash Join]         耗时: %.3f 毫秒\n", hashJoinMs);

        // --- 测试3: Index Nested Loop Join（索引嵌套循环连接）---
        double indexJoinMs = testIndexNestedLoopJoin(bpm, catalog, finalSchema);
        System.out.printf("--> 测试3: [Index Nested Loop Join] 耗时: %.3f 毫秒\n", indexJoinMs);

        // --- 3. 结果总结：对比三种算法的性能差异 ---
        System.out.println("\n--- 性能对比总结 ---");
        System.out.printf("    Nested Loop Join:      %.3f ms (基准)\n", nlJoinMs);
        System.out.printf("    Hash Join:             %.3f ms (性能提升 %.2f 倍)\n", hashJoinMs, nlJoinMs / hashJoinMs);
        System.out.printf("    Index Nested Loop Join: %.3f ms (性能提升 %.2f 倍)\n", indexJoinMs, nlJoinMs / indexJoinMs);

        System.out.println("\n============== 演示结束 ==============");
    }

    /**
     * 测试嵌套循环连接性能
     * 原理：外表（orders）的每条记录与内表（customers）的所有记录逐一比对，满足条件则连接
     * @param bpm 缓冲池管理器
     * @param catalog 元数据目录
     * @param finalSchema 连接结果的schema
     * @return 执行耗时（毫秒）
     */
    private static double testNestedLoopJoin(BufferPoolManager bpm, Catalog catalog, Schema finalSchema) {
        // 创建外表（orders）扫描执行器：遍历所有订单记录
        Executor ordersScan = new SeqScanExecutor(bpm, catalog, "orders");
        // 创建内表（customers）扫描执行器：遍历所有顾客记录
        Executor customersScan = new SeqScanExecutor(bpm, catalog, "customers");

        // 定义连接条件：orders.o_customer_id（索引1） = customers.c_id（索引0）
        Predicate joinPredicate = new Predicate(1, 0);

        // 创建嵌套循环连接执行器
        Executor joinExecutor = new NestedLoopJoinExecutor(ordersScan, customersScan, joinPredicate, finalSchema);

        // 执行并计时
        return executeAndMeasure(joinExecutor);
    }

    /**
     * 测试哈希连接性能
     * 原理：1. 用内表（小表customers）构建哈希表（键为c_id）；2. 扫描外表（大表orders），用o_customer_id查哈希表
     * @param bpm 缓冲池管理器
     * @param catalog 元数据目录
     * @param finalSchema 连接结果的schema
     * @return 执行耗时（毫秒）
     */
    private static double testHashJoin(BufferPoolManager bpm, Catalog catalog, Schema finalSchema) {
        // 创建探测端（Probe side）执行器：扫描大表orders（需匹配的表）
        Executor ordersScan = new SeqScanExecutor(bpm, catalog, "orders");
        // 创建构建端（Build side）执行器：扫描小表customers（用于构建哈希表）
        Executor customersScan = new SeqScanExecutor(bpm, catalog, "customers");

        // 定义连接条件：orders.o_customer_id（索引1） = customers.c_id（索引0）
        Predicate joinPredicate = new Predicate(1, 0);

        // 创建哈希连接执行器
        Executor joinExecutor = new HashJoinExecutor(ordersScan, customersScan, joinPredicate, finalSchema);

        // 执行并计时
        return executeAndMeasure(joinExecutor);
    }

    /**
     * 测试索引嵌套循环连接性能
     * 原理：利用内表（customers）的主键索引（c_id），外表（orders）每条记录通过索引快速查找匹配的内表记录
     * @param bpm 缓冲池管理器
     * @param catalog 元数据目录
     * @param finalSchema 连接结果的schema
     * @return 执行耗时（毫秒）
     */
    private static double testIndexNestedLoopJoin(BufferPoolManager bpm, Catalog catalog, Schema finalSchema) {
        // 创建外表扫描执行器：遍历所有订单记录
        Executor ordersScan = new SeqScanExecutor(bpm, catalog, "orders");
        // 获取内表元数据（customers表，包含主键索引）
        Catalog.TableMetadata innerTable = catalog.getTable("customers");

        // 定义外表中用于连接的字段索引：orders.o_customer_id在订单表schema中的索引为1
        int outerJoinKeyIndex = 1;

        // 创建索引嵌套循环连接执行器（自动使用内表的主键索引）
        Executor joinExecutor = new IndexNestedLoopJoinExecutor(ordersScan, innerTable, outerJoinKeyIndex, bpm, finalSchema);

        // 执行并计时
        return executeAndMeasure(joinExecutor);
    }

    /**
     * 辅助方法：执行查询计划并计算耗时（毫秒）
     * @param executor 待执行的查询计划执行器
     * @return 执行耗时（毫秒）
     */
    private static double executeAndMeasure(Executor executor) {
        long startTime = System.nanoTime(); // 开始时间（纳秒级）

        // 执行查询计划
        executor.open(); // 初始化执行器
        int rowCount = 0; // 记录匹配的记录数
        // 循环获取所有结果记录，直到没有更多记录
        while (executor.next() != null) {
            rowCount++;
        }
        executor.close(); // 关闭执行器，释放资源

        long endTime = System.nanoTime(); // 结束时间（纳秒级）

        // 打印匹配记录数，返回耗时（转换为毫秒）
        System.out.printf("    (查询完成，共找到 %d 条匹配记录)", rowCount);
        return (endTime - startTime) / 1_000_000.0;
    }
}